package com.dhl.fin.api.service;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUnit;
import com.dhl.fin.api.common.dto.QueryDto;
import com.dhl.fin.api.common.service.CommonService;
import com.dhl.fin.api.common.util.*;
import com.dhl.fin.api.constant.RemindConstant;
import com.dhl.fin.api.dao.MyRemindDao;
import com.dhl.fin.api.domain.ExtraJobCron;
import com.dhl.fin.api.domain.MensesRemindTask;
import com.dhl.fin.api.domain.RemindTask;
import com.dhl.fin.api.domain.RemindType;
import com.dhl.fin.api.dto.NextTimeDTO;
import com.dhl.fin.api.dto.RemindCardInfoDTO;
import com.dhl.fin.api.dto.RemindTypeInfoDTO;
import com.dhl.fin.api.enums.AutoDealWayEnum;
import com.dhl.fin.api.enums.RemindCycleTypeEnum;
import com.dhl.fin.api.enums.RemindDateTypeEnum;
import com.dhl.fin.api.enums.WeekEnum;
import com.dhl.fin.api.service.cronService.RemindCron;
import com.nlf.calendar.Lunar;
import lombok.extern.slf4j.Slf4j;
import org.quartz.CronExpression;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author CuiJianbo
 * @since 2025/5/4
 */
@Slf4j
@Service
@Transactional(rollbackFor = Exception.class)
public class RemindTaskService extends CommonService<RemindTask> {

    @Autowired
    RemindTypeService remindTypeService;

    @Autowired
    ExtraJobCronService extraJobCronService;

    @Autowired
    LoginAccountService loginAccountService;

    @Autowired
    MyRemindDao myRemindDao;

    public void createNewRemindTask(RemindTask remindTask) throws Exception {
        String unicode = WebUtil.getLoginUser().getUuid();
        RemindType remindType = remindTypeService.queryTypeByName(remindTask.getRemindTypeName(), unicode);
        RemindCron remindCron = RemindCycleTypeEnum.getRemindCycleTypeRemindCron(remindTask.getRemindCycleWay());
        remindTask.setUnicode(unicode);
        remindTask.setRemindType(remindType);
        remindTask.setCron(remindCron.calculateCron(remindTask));

        //创建remindTask业务数据
        saveDomain(remindTask);

        //刷新定时任务
        freshTaskCronJob(remindTask);

    }

    public void freshTaskCronJob(RemindTask remindTask) throws Exception {

        if (ObjectUtil.notNull(remindTask.getId())) {
            //注销定时任务
            extraJobCronService.cancelExtraJob(remindTask.getId());
        }

        //如果启用了每次重复提醒，就要生成额外的定时任务
        extraJobCronService.generateExtraJob(remindTask);
    }

    public List<RemindTypeInfoDTO> remindInitData() throws Exception {
        String unicode = WebUtil.getLoginUser().getUuid();
        List<RemindType> remindTypes = remindTypeService.getInitHomeRemindTypes(unicode);
        remindTypeService.checkHasRemindType(unicode);
        RemindTypeInfoDTO allRemindTypeInfos = new RemindTypeInfoDTO();
        allRemindTypeInfos.setRemindTypeName(allRemindTypeInfos.getRemindTypeName());
        allRemindTypeInfos.setRemindTypeName("全部");
        allRemindTypeInfos.setIcon("icon0.png");
        allRemindTypeInfos.setSort(-1);
        allRemindTypeInfos.setIsFix(true);
        allRemindTypeInfos.setIsActive(true);

        List<Long> remindTaskIDs = remindTypes.stream().map(RemindType::getRemindTasks).flatMap(List::stream).map(RemindTask::getId).collect(Collectors.toList());
        Map<Long, List<ExtraJobCron>> taskAndExtralMap = extraJobCronService.selectExtraJobFromRemindTask(remindTaskIDs);
        List<RemindTypeInfoDTO> otherRemindTypeInfos = new LinkedList<>();
        for (RemindType remindType : remindTypes) {
            RemindTypeInfoDTO remindTypeInfoDTO = new RemindTypeInfoDTO();
            otherRemindTypeInfos.add(remindTypeInfoDTO);
            List<RemindCardInfoDTO> cardInfoDTOS = new LinkedList<>();
            ObjectUtil.copyFieldValue(remindType, remindTypeInfoDTO);

            for (RemindTask remindTask : remindType.getRemindTasks()) {
                RemindCardInfoDTO remindCardInfoDTO = new RemindCardInfoDTO();
                Date nowDate = DateUtil.getSysDate();

                if (remindTask.getIsSetEndDate()) {
                    if (ObjectUtil.notNull(remindTask.getStartDate()) && nowDate.before(remindTask.getStartDate())) {
                        nowDate = remindTask.getStartDate();
                    } else if (ObjectUtil.notNull(remindTask.getEndDate()) && nowDate.after(remindTask.getEndDate())) {
                        if (remindTask.getAutoDealWay().equals(AutoDealWayEnum.DISABLED.getCode())) {
                            remindCardInfoDTO.setAlertType(RemindConstant.TIP_TYPE_DISABLED);
                        }
                    }
                }

                Date targetDate = null;
                List<ExtraJobCron> extraJobCrons = taskAndExtralMap.get(remindTask.getId());
                if (CollectorUtil.isNoTEmpty(extraJobCrons)) {
                    extraJobCrons.sort(Comparator.comparing(ExtraJobCron::getExecuteTime));
                    targetDate = extraJobCrons.get(0).getExecuteTime();
                }

                remindCardInfoDTO.setIsActive(remindTask.getIsActive());
                remindCardInfoDTO.setRemindTypeName(remindType.getRemindTypeName());
                remindCardInfoDTO.setRemindName(remindTask.getRemindName());
                remindCardInfoDTO.setId(remindTask.getId());
                remindCardInfoDTO.setIsTopShow(remindTask.getIsTopShow());

                if (ObjectUtil.notNull(targetDate)) {
                    SimpleDateFormat fullSdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
                    remindCardInfoDTO.setWeekNum(WeekEnum.getWeekNameByDate(targetDate));
                    remindCardInfoDTO.setNextExecuteTime(fullSdf.format(targetDate));
                    remindCardInfoDTO.setNextExecuteDate(targetDate);

                    long hours = cn.hutool.core.date.DateUtil.between(DateUtil.getSysDate(), targetDate, DateUnit.HOUR);
                    if (hours == 0) {
                        long minutes = cn.hutool.core.date.DateUtil.between(DateUtil.getSysDate(), targetDate, DateUnit.MINUTE);
                        if (minutes == 0) {
                            long second = cn.hutool.core.date.DateUtil.between(DateUtil.getSysDate(), targetDate, DateUnit.SECOND);
                            remindCardInfoDTO.setBalanceText(second + RemindConstant.TIME_UNIT_CN_NAME_SECOND);
                        } else {
                            remindCardInfoDTO.setBalanceText(minutes + RemindConstant.TIME_UNIT_CN_NAME_MINUTES);
                        }
                        remindCardInfoDTO.setAlertType(RemindConstant.TIP_TYPE_WARNING);
                    } else if (hours < 24) {
                        remindCardInfoDTO.setBalanceText(hours + RemindConstant.TIME_UNIT_CN_NAME_HOUR);
                        remindCardInfoDTO.setAlertType(RemindConstant.TIP_TYPE_WARNING);
                    } else {
                        remindCardInfoDTO.setBalanceText(((hours / 24) + 1) + RemindConstant.TIME_UNIT_CN_NAME_DAY);
                        String type = (hours / 24) < 10 ? RemindConstant.TIP_TYPE_WARNING : RemindConstant.TIP_TYPE_NORMAL;
                        remindCardInfoDTO.setAlertType(type);
                    }

                    if (!remindTask.getIsActive()) {
                        remindCardInfoDTO.setAlertType(RemindConstant.TIP_TYPE_DISABLED);
                    }

                } else {
                    remindCardInfoDTO.setAlertType(RemindConstant.TIP_TYPE_DISABLED);
                    remindCardInfoDTO.setIsActive(false);
                }

                cardInfoDTOS.add(remindCardInfoDTO);

            }

            for (MensesRemindTask remindTask : remindType.getMensesRemindTasks()) {
                ExtraJobCron extraJobCron = extraJobCronService.getFirstExtraJobCronByRemindTaskId(remindTask.getId());
                if (ObjectUtil.isNull(extraJobCron)) {
                    continue;
                }
                SimpleDateFormat fullSdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
                Timestamp next = extraJobCron.getExecuteTime();
                RemindCardInfoDTO remindCardInfoDTO = new RemindCardInfoDTO();
                remindCardInfoDTO.setRemindTypeName(remindType.getRemindTypeName());
                remindCardInfoDTO.setRemindName(remindTask.getRemindName());
                remindCardInfoDTO.setNextExecuteTime(fullSdf.format(next));
                remindCardInfoDTO.setNextExecuteDate(next);
                remindCardInfoDTO.setWeekNum(WeekEnum.getWeekNameByDate(next));
                remindCardInfoDTO.setIsTopShow(remindTask.getIsTopShow());
                remindCardInfoDTO.setId(remindTask.getId());
                remindCardInfoDTO.setIsActive(remindTask.getIsActive());

                long hours = cn.hutool.core.date.DateUtil.between(DateUtil.getSysDate(), next, DateUnit.HOUR);
                if (hours == 0) {
                    long minutes = cn.hutool.core.date.DateUtil.between(DateUtil.getSysDate(), next, DateUnit.MINUTE);
                    remindCardInfoDTO.setBalanceText(minutes + RemindConstant.TIME_UNIT_CN_NAME_MINUTES);
                    remindCardInfoDTO.setAlertType(RemindConstant.TIP_TYPE_WARNING);
                } else if (hours < 24) {
                    remindCardInfoDTO.setBalanceText(hours + RemindConstant.TIME_UNIT_CN_NAME_HOUR);
                    remindCardInfoDTO.setAlertType(RemindConstant.TIP_TYPE_WARNING);
                } else {
                    remindCardInfoDTO.setBalanceText(((hours / 24) + 1) + RemindConstant.TIME_UNIT_CN_NAME_DAY);
                    String type = (hours / 24) < 10 ? RemindConstant.TIP_TYPE_WARNING : RemindConstant.TIP_TYPE_NORMAL;
                    remindCardInfoDTO.setAlertType(type);
                }

                if (!remindTask.getIsActive()) {
                    remindCardInfoDTO.setAlertType(RemindConstant.TIP_TYPE_DISABLED);
                }
                cardInfoDTOS.add(remindCardInfoDTO);
            }

            if (CollectionUtil.isNotEmpty(cardInfoDTOS)) {
                List<RemindCardInfoDTO> data1 = cardInfoDTOS.stream()
                        .filter(RemindCardInfoDTO::getIsActive)
                        .sorted(Comparator.comparing(RemindCardInfoDTO::getIsTopShow).reversed()
                                .thenComparing(RemindCardInfoDTO::getIsActive))
                        .collect(Collectors.toList());
                List<RemindCardInfoDTO> data2 = cardInfoDTOS.stream()
                        .filter(p -> !p.getIsActive())
                        .collect(Collectors.toList());
                data1.addAll(data2);
                remindTypeInfoDTO.setRemindTasks(data1);
            }
        }


        List<RemindCardInfoDTO> data1 = otherRemindTypeInfos.stream()
                .map(RemindTypeInfoDTO::getRemindTasks)
                .filter(Objects::nonNull)
                .flatMap(List::stream)
                .filter(RemindCardInfoDTO::getIsActive)
                .sorted(Comparator.comparing(RemindCardInfoDTO::getIsTopShow).reversed()
                        .thenComparing(RemindCardInfoDTO::getNextExecuteDate))
                .collect(Collectors.toList());

        List<RemindCardInfoDTO> data2 = otherRemindTypeInfos.stream()
                .map(RemindTypeInfoDTO::getRemindTasks)
                .filter(Objects::nonNull)
                .flatMap(List::stream)
                .filter(p -> !p.getIsActive())
                .collect(Collectors.toList());
        data1.addAll(data2);
        allRemindTypeInfos.setRemindTasks(data1);

        List<RemindTypeInfoDTO> remindTypeList = new LinkedList<>();
        remindTypeList.add(allRemindTypeInfos);
        remindTypeList.addAll(otherRemindTypeInfos);

        return remindTypeList;
    }


    public void updateTaskType(List<RemindType> taskTypes) {
        String unicode = WebUtil.getLoginUser().getUuid();
        for (RemindType taskType : taskTypes) {
            taskType.setUnicode(unicode);
        }

        myRemindDao.deleteRemindType(unicode);

        myRemindDao.batchInsertRemindType(taskTypes);
    }


    public List<RemindType> selectAllRemindType() throws Exception {
        String unicode = WebUtil.getLoginUser().getUuid();
        return remindTypeService.select(QueryDto.builder()
                .available()
                .addWhere("unicode = '" + unicode + "'")
                .build());
    }


    public void deleteRemind(Long remindTaskId) throws Exception {
        extraJobCronService.cancelExtraJob(remindTaskId);
        deleteById(remindTaskId);
    }

    public List<RemindTask> getAllRemindTask() throws Exception {
        return select(QueryDto.builder()
                .available()
                .build());
    }

    public List<NextTimeDTO> getNextFiveExecuteTime(RemindTask remindTask) throws Exception {
        RemindCron remindCron = RemindCycleTypeEnum.getRemindCycleTypeRemindCron(remindTask.getRemindCycleWay());
        String cronExpress = remindCron.calculateCron(remindTask);
        int dulTimes = remindTask.getRemindCycleWay().equals("一次性") ? 1 : 5;
        CronExpression cron = new CronExpression(cronExpress);
        List<NextTimeDTO> nextTimesDto = new ArrayList<>();
        Date startDate = DateUtil.getSysDate();

        if (remindTask.getIsSetEndDate()) {
            if (ObjectUtil.notNull(remindTask.getStartDate()) && startDate.before(remindTask.getStartDate())) {
                startDate = remindTask.getStartDate();
            }
            if (ObjectUtil.notNull(remindTask.getEndDate()) && startDate.after(remindTask.getEndDate())) {
                return Collections.emptyList();
            }
        } else if (remindTask.getDateType().equals(RemindDateTypeEnum.GREGORIAN.getName())
                && remindTask.getRemindCycleWay().equals(RemindCycleTypeEnum.ONE_TIMES.getName())
                && remindTask.getRemindDate().compareTo(DateUtil.getSysDate()) > 0
        ) {
            int remindYear = cn.hutool.core.date.DateUtil.year(remindTask.getRemindDate());
            startDate = DateUtil.getDate(StringUtil.join(remindYear, "-01-01"));
        }

        if (remindTask.getDateType().equals(RemindDateTypeEnum.GREGORIAN.getName())) {
            Date startDateTime = startDate;
            for (int i = 0; i < dulTimes; i++) {
                Date next = cron.getNextValidTimeAfter(startDateTime);
                if (remindTask.getRemindCycleWay().equals(RemindCycleTypeEnum.ONE_TIMES.getName())) {
                    if (cn.hutool.core.date.DateUtil.year(remindTask.getRemindDate()) == cn.hutool.core.date.DateUtil.year(next)) {
                        nextTimesDto.add(NextTimeDTO.changeToNextTime(next, next, RemindConstant.ON_TIME_TIP));
                        startDateTime = next;
                    }
                } else {
                    nextTimesDto.add(NextTimeDTO.changeToNextTime(next, next, RemindConstant.ON_TIME_TIP));
                    startDateTime = next;
                }
            }
        } else if (remindTask.getDateType().equals(RemindDateTypeEnum.SOLAR.getName())) {
            String[] indexs = remindTask.getLunarDayIndex().split(",");
            String[] remindTime = remindTask.getRemindTime().split(":");
            int year = cn.hutool.core.date.DateUtil.year(startDate);
            Lunar midAutumn = new Lunar(year, Integer.parseInt(indexs[0]) + 1, Integer.parseInt(indexs[1]) + 1);
            int startIndex = DateUtil.getFullDate(String.format("%s %s:%s:00", midAutumn.getSolar().toString(), remindTime[0], remindTime[1])).compareTo(startDate) > 0 ? 0 : 1;
            for (int i = startIndex; i < dulTimes + startIndex; i++) {
                midAutumn = new Lunar(year + i, Integer.parseInt(indexs[0]) + 1, Integer.parseInt(indexs[1]) + 1);
                Date next = DateUtil.getFullDate(String.format("%s %s:%s:00", midAutumn.getSolar().toString(), remindTime[0], remindTime[1]));
                nextTimesDto.add(NextTimeDTO.changeToNextTime(next, next, RemindConstant.ON_TIME_TIP));
            }
        }

        if (CollectorUtil.isEmpty(nextTimesDto)) {
            return Collections.emptyList();
        }

        //添加重复提醒的任务
        if (remindTask.getIsMoreTip()) {
            addCycleNextTimes(remindTask, dulTimes, nextTimesDto, false);
        }

        //添加提前提醒的任务
        if (remindTask.getIsPrevTip()) {
            addCycleNextTimes(remindTask, dulTimes, nextTimesDto, true);
        }

        //补全数据库里的extraJob任务
        if (WebUtil.getBooleanParam("isInit") && ObjectUtil.notNull(remindTask.getId()) && remindTask.getId() > 0) {
            List<NextTimeDTO> jobNextTime = extraJobCronService.getExtraJobCronByTaskId(remindTask.getId())
                    .stream()
                    .map(ExtraJobCron::changeToNextTimeDTO)
                    .collect(Collectors.toList());
            nextTimesDto.addAll(jobNextTime);
        }
        Date startDateTime = startDate;


        /*
          1、用开始时间和结束时间过滤
          2、按照执行时间去重
          3、执行时间排序
          4、截取前5条提醒任务
         */
        return nextTimesDto.stream()
                .filter(p -> {
                    Timestamp date = DateUtil.getFullTimestamp(p.getNextTime());
                    boolean minDate = date.after(new Timestamp(startDateTime.getTime()));
                    boolean maxDate = true;
                    if (remindTask.getIsSetEndDate() && ObjectUtil.notNull(remindTask.getEndDate())) {
                        maxDate = remindTask.getEndDate().compareTo(date) >= 0;
                    }
                    return minDate && maxDate;
                })
                .collect(Collectors.toMap(NextTimeDTO::getNextTime, p -> p, (x, y) -> x))
                .values()
                .stream()
                .sorted(Comparator.comparing(NextTimeDTO::getNextTime, Comparator.comparing(DateUtil::getFullTimestamp)
                ))
                .limit(5)
                .collect(Collectors.toList());

    }

    private void addCycleNextTimes(RemindTask remindTask, int dulTimes, List<NextTimeDTO> nextTimesDto, boolean isPrevTip) throws Exception {
        Date now = DateUtil.getSysDate();
        int count = 0;
        String timesCycle = isPrevTip ? remindTask.getPrevTimesCycle() : remindTask.getTimesCycle();
        String durationTime = isPrevTip ? remindTask.getPrevDurationTime() : remindTask.getDurationTime();
        String tipRemark = isPrevTip ? RemindConstant.PRE_TIME_TIP : RemindConstant.CYCLE_TIME_TIP;
        int n = Integer.parseInt(timesCycle.substring(0, 1));
        for (int j = 0; j < dulTimes; j++) {
            Date nextDate = cn.hutool.core.date.DateUtil.parseDateTime(nextTimesDto.get(j).getNextTime());
            int timeCount;
            for (int i = 1; i <= n; i++) {
                Timestamp dateTime = null;
                if (durationTime.endsWith(RemindConstant.TIME_UNIT_CN_NAME_MINUTES)) {
                    timeCount = Integer.parseInt(durationTime.replace(RemindConstant.TIME_UNIT_CN_NAME_MINUTES, ""));
                    timeCount = isPrevTip ? -timeCount : timeCount;
                    dateTime = cn.hutool.core.date.DateUtil.offsetMinute(nextDate, timeCount * i).toTimestamp();
                } else if (durationTime.endsWith(RemindConstant.TIME_UNIT_CN_NAME_HOUR)) {
                    timeCount = Integer.parseInt(durationTime.replace(RemindConstant.TIME_UNIT_CN_NAME_HOUR, ""));
                    timeCount = isPrevTip ? -timeCount : timeCount;
                    dateTime = cn.hutool.core.date.DateUtil.offsetHour(nextDate, timeCount * i).toTimestamp();
                } else if (durationTime.endsWith(RemindConstant.TIME_UNIT_CN_NAME_DAY)) {
                    timeCount = Integer.parseInt(durationTime.replace(RemindConstant.TIME_UNIT_CN_NAME_DAY, ""));
                    timeCount = isPrevTip ? -timeCount : timeCount;
                    dateTime = cn.hutool.core.date.DateUtil.offsetDay(nextDate, timeCount * i).toTimestamp();
                }
                if (dateTime.after(now) && count < 6) {
                    count++;
                    nextTimesDto.add(NextTimeDTO.changeToNextTime(dateTime, nextDate, tipRemark));
                }
            }
        }

        if (ObjectUtil.notNull(remindTask.getId()) && remindTask.getId() > 0) {
            extraJobCronService.getExtraJobCronByTaskId(remindTask.getId());
        }
    }

    public Integer taskCount(String unicode) throws Exception {
        return count(QueryDto.builder()
                .addWhere("unicode = '" + unicode + "'")
                .build());
    }

    public Boolean isNewUser(String unicode) throws Exception {

        boolean countIsZero = loginAccountService.getAccountAllowCount() == 0;

        boolean taskCountIsZero = count(QueryDto.builder()
                .addWhere("unicode = '" + unicode + "'")
                .build()) == 0;

        return countIsZero || taskCountIsZero;

    }

}